/*
 * Copyright the original author or authors.
 * 
 * Licensed under the MOZILLA PUBLIC LICENSE, Version 1.1 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.mozilla.org/MPL/MPL-1.1.html
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import com.bourre.log.PixlibStringifier;
import com.bourre.transitions.IFrameListener;
import com.kairos.engine.RTEvent;
import com.kairos.engine.RTObject;
import com.kairos.motion.strategy.IPlayStrategy;
import com.kairos.motion.strategy.LoopStrategy;
import com.kairos.motion.strategy.NoLoopStrategy;
import com.kairos.motion.strategy.ReversedLoopStrategy;
import com.kairos.motion.strategy.ReversedNoLoopStrategy;

/**
 * The {@code AbstractAnimation} object offer to play an animation in a different
 * way than the Flash Player way. It's provide all basics behaviors to all others
 * objects in kairos witch deal with frames. 
 * 
 * <p>Animation is played according to the {@code RTBeacon} internal workflow.
 * On each frame, the timestep is added to the internal time clock, the current
 * frame gap is defined with the equation below, where <i>timestep</i> is the rest
 * of time of the last call plus the current frame duration.</p>
 * 
 * <code>gap = ( timestep - ( timestep % framerate ) ) / framerate</code>
 * 
 * <p>If the gap is different than 0 (framerate of animation > framerate of the
 * {@code AbstractAnimation}), the playhead is moved. If the gap is greater than 1,
 * frames are skipped but animation stop immediatly if the loop parameter is set
 * to {@false} and if the playhead will go past total frames.</p>
 * 
 * <p>The {@code AbstractAnimation} use the Strategy pattern to perform the animation,
 * each time you change an animation paramater such <i>loop</i> or <i>reverse</i>
 * the strategy is changed, in that way there is the minimum statements required
 * to perform the animation.</p>
 * 
 * <p>Extends this class to create your own animation controller and defining on witch
 * objects the animation deal. The only method you have to override is the {@link #_frameChanged}
 * method.</p>
 * 
 * @author  Cédric Néhémie
 * @see		IFrameListener
 * @see		RTObject
 */
class com.kairos.motion.AbstractAnimation extends RTObject implements IFrameListener 
{
	
	private static var LOOP : IPlayStrategy = new LoopStrategy();
	private static var NO_LOOP : IPlayStrategy = new NoLoopStrategy();
	private static var REVERSED_LOOP : IPlayStrategy = new ReversedLoopStrategy();
	private static var REVERSED_NO_LOOP : IPlayStrategy = new ReversedNoLoopStrategy();
	
	private var _n : Number;
	private var _f : Number;
	private var _tf : Number;
	private var _fr : Number;
	private var _l : Boolean;
	private var _r : Boolean;
	private var _oS : IPlayStrategy;

	/**
	 * Call the super constructor to defines basic behaviors for your own objects.
	 * 
	 * <p>The animation is played at {@code fps} speed.</p>
	 * 
	 * <p>If {@code loop} argument is omitted then {@code loop} is set
	 * to {@code true}.</p>
	 * 
	 * <p>If {@code reverse} argument is omitted then {@code reverse}
	 * is set to {@code false}.</p>
	 * 
	 * @param   fps {@code Number} of frame per seconds of the animation.
	 * @param   l   {@code true} to make the animarion looping.
	 * @param	r	{@code true} to make the animation playing reversed. 
	 */
	private function AbstractAnimation( fps : Number, l : Boolean, r : Boolean ) 
	{
		setFramerate( fps );
		setReverse( r );
		setLoop ( l );
		reset();
	}
	/**
	 * Don't use, overwrite or override this method.
	 * 
	 * <p>That's the public callback of {@link com.kairos.engine.RTBeacon}.</p>
	 */
	public function onEnterFrame () : Void
	{
		var t : Number = _n + getLocalStep( arguments[ 0 ] );
		var f : Number =  ( t - ( _n = t % _fr ) ) / _fr;
		if( f ) _movePlayHead ( f );
	}
	
	/**
	 * Defines the speed at witch the animation is played.
	 * 
	 * @param   n	The number of frame per seconds.
	 * @example	mch.setFramerate ( 50 );
	 */
	public function setFramerate ( n : Number ) : Void
	{
		_fr = ( !n || n < 0) ? 25 : 1000 / n;
	}
	/**
	 * Return the speed of the animation.
	 * 
	 * @return  The number of frames per seconds.
	 */
	public function getFramerate () : Number
	{
		return 1000 / _fr;
	}
	
	/**
	 * Defines if the animation loop at the end
	 * of the timeline.
	 * 
	 * @param	b	Does the animation loop ? 
	 */
	public function setLoop ( b : Boolean ) : Void
	{
		_l = b;
		_switchStrategy ();
	}
	
	/**
	 * Does the animation loop ? 
	 * 
	 * @return {@code true} if the animtion loop, {@code false} otherwise.
	 */
	 public function getLoop () : Boolean
	 {
	 	return _l;
	 }
	 	
	/**
	 * Resets the animation.
	 * 
	 * <p>If the object is running the animation just restart
	 * from the first or the last frame according to the object
	 * play mode.</p>
	 */
	 public function reset() : Void
	 {
	 	_n = 0;
	 	if( _r ) _f = _tf;
		else _f = 1;
		
		_frameChanged( _f );
	 }
	
	/**
	 * Reverses the way the animation run.
	 * 
	 * <p>When animation is reversed and loop property is set
	 * to {@code false} the animation stop when arriving at the
	 * first frame.</p>
	 */
	public function reverse () : Void
	{
		setReverse ( !_r );
	}
	
	/**
	 * Defines the way the animation run.
	 * 
	 * @param	rev {@code true} if the animation run in the reversed way, 
	 * 				{@code false} otherwise.
	 */
	public function setReverse( rev : Boolean) : Void 
	{
		_r = rev;
		_switchStrategy();
	}
	
	/**
	 * Returns the way of the animation.
	 * 
	 * @return	{@code true} if the animation run in the reversed way, 
	 * 			{@code false} otherwise.
	 */
	public function getReverse () : Boolean
	{
		return _r;
	}
	
	/**
	 * Use the strategy to produce animation.
	 *  	 
	 * Stop the animation if the strategy return
	 * {@code true} in {@code _isMotionFinished}.
	 */
	private function _movePlayHead ( f : Number ) : Void
	 {
	 	if( _oS.isMotionFinished ( _f, _tf ,f ) )
	 	{
	 		super.stop();
	 		fireCommandEndEvent(); 
	 	}
	 	_f = _oS.getNextFrame( _f, _tf, f );
	 	_frameChanged ( _f );		
	 } 
	
	/**
	 * Select the play strategy according to all parameters
	 */
	private function _switchStrategy () : Void
	{
		if( _l )
		{
			if( _r ) _oS = REVERSED_LOOP;
			else _oS = LOOP;
		}
		else
		{
			if( _r ) _oS = REVERSED_NO_LOOP;
			else _oS = NO_LOOP;

		}
	}
	
	/**
	 * Called when frame changed according to internal computation.
	 * 
	 * <p>Override that methods to create your own animation controller.</p>
	 * 
	 * @param	f	{@code Number} of frame the animation have to gap.
	 */
	private function _frameChanged ( f : Number ) : Void {}
	
	/**
	 * Return the String representation of the object. 
	 */	
	public function toString() : String
	{
		return PixlibStringifier.stringify( this );
	}
}